package ipc;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.DecimalFormat;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.xerces.parsers.DOMParser;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import rddl.EvalException;
import rddl.RDDL;
import rddl.RDDL.DOMAIN;
import rddl.RDDL.INSTANCE;
import rddl.RDDL.LCONST;
import rddl.RDDL.NONFLUENTS;
import rddl.RDDL.PVAR_INST_DEF;
import rddl.RDDL.PVAR_NAME;
import rddl.State;
import rddl.competition.RDDLXMLException;
import rddl.competition.Server;
import rddl.parser.parser;
import rddl.policy.Policy;
import rddl.policy.RandomBoolPolicy;
import rddl.policy.RandomEnumPolicy;
import rddl.viz.StateViz;
/** The SocketClient class is a simple example of a TCP/IP Socket Client.
 *
 */

public class Relay {
	
	enum XMLType {
		ROUND,TURN,ROUND_END,END_TEST,NONAME
	}

	public int numRounds = 0;
	public double timeAllowed = 0;
	public static int curRound = 0;
	public double reward = 0.0;
	public int id = 0;
	public static int portNum;
	public static String hostName;
	final static String clientName = "Beaver";
	static public InetAddress address ;
	static public Socket connection;
	static public BufferedOutputStream bos;
	static public OutputStreamWriter osw;
	static public BufferedInputStream bis;
	static public DOMParser p;
	
	public static void init(String host, int port){
		hostName = host;
		portNum = port;
		System.out.println("Cpnnecting...");
		/** Obtain an address object of the server */
		try {
			address = InetAddress.getByName(hostName);
		} catch (UnknownHostException e) {
//			Main.exceptionLog.log("cannot get server address by name\n"+e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
		/** Establish a socket connetion */
		try {
			connection = new Socket(address, portNum);
		} catch (IOException e) {
			e.printStackTrace();
//			Main.exceptionLog.log("cannot establish socket connection\n"+e.getMessage());
			System.exit(1);
		}
		
		/** Instantiate a BufferedOutputStream object */
		try {
			bos = new BufferedOutputStream(connection.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
//			Main.exceptionLog.log("exception in getting BOS\n"+e.getMessage());
			System.exit(1);
		}
		/** Instantiate an OutputStreamWriter object with the optional character
		 * encoding.
		 */
		try {
			osw = new OutputStreamWriter(bos, "US-ASCII");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
//			Main.exceptionLog.log("unsupported encoding\n"+e.getMessage());
			System.exit(1);
		}
		/** Write across the socket connection and flush the buffer */
		
		try {
			bis = new BufferedInputStream(connection.getInputStream());
		} catch (IOException e) {
//			Main.exceptionLog.log("Cannot get input stream top socket\n"+e.getMessage());
			System.exit(1);
		}
		
		p = new DOMParser();
	}
	
	public static void reinit(){
//		if( )
//		try{
//			
//		}
	}
	
	public Relay() {
		numRounds = 0;
		timeAllowed = 0;
		curRound = 0;
		reward = 0;
		id = 0;
	}

	public static Relay sessionInit(String instance_name){
		
		String msg = createXMLSessionRequest(instance_name, clientName);
		if(msg == null){
			System.exit(1);
		}
//		Main.XMLLog.log(msg);
		try {
			Server.sendOneMessage(osw, msg);
		} catch (IOException e) {
			Main.exceptionLog.log("cannot send message\n"+e.getMessage());
			System.exit(1);
		}
		
		InputSource isrc = Server.readOneMessage(bis);
		Relay relay = new Relay();
		try {
			relay = processXMLSessionInit(p, isrc);
		} catch (RDDLXMLException e) {
			Main.exceptionLog.log("XML exception in sessioninit\n"+e.getMessage());
			System.exit(1);
		}
		return relay;
	}

	public static double roundRequest(){
		String msg = createXMLRoundRequest();
		if(msg == null){
			System.exit(1);
		}
//		Main.XMLLog.log(msg);
		try {
			Server.sendOneMessage(osw, msg);
		} catch (IOException e) {
			Main.exceptionLog.log("Cannot send message roundrequest\n"+e.getMessage());
			System.exit(1);
		}
		InputSource isrc = Server.readOneMessage(bis);
		double timeLeft = 0;
		try {
			timeLeft = processXMLRoundInit(p, isrc, curRound+1);
		} catch (RDDLXMLException e) {
//			Main.exceptionLog.log("XML exception in processing round init\n"+e.getMessage());
			System.exit(1);
		}
		++curRound;
		return timeLeft;
	}
	
	public static boolean readTurnMessage(State state){
		InputSource isrc = Server.readOneMessage(bis);
		ArrayList<PVAR_INST_DEF> obs = null;
		try {
			obs = processXMLTurn(p,isrc,state);
		} catch (RDDLXMLException e) {
			Main.exceptionLog.log("Xml exception in processing turn message\n"+e.getMessage());
			System.exit(1);
		}
		if ( obs == null ) {
			System.err.println("No state/observations received.");
			return false;
		} else {
			state.clearPVariables(state._state);
			state.setPVariables(state._state, obs);
		}
		return true;
	}
	
	public static void sendAction(ArrayList<PVAR_INST_DEF> actions){
		String msg = createXMLAction(actions);
//		Main.XMLLog.log(msg);
		try {
			Server.sendOneMessage(osw, msg);
		} catch (IOException e) {
			Main.exceptionLog.log("Cannot send message: action\n"+e.getMessage());
			System.exit(1);
		}
	}
	
	public static double roundEnd(){
		InputSource isrc = Server.readOneMessage(bis);
		double reward = 0;
		try {
			reward = processXMLRoundEnd(p, isrc);
		} catch (RDDLXMLException e) {
			Main.exceptionLog.log("xml exception in processing round end\n"+e.getMessage());
			System.exit(1);
		}
		return reward;
	}
	
	public static double sessionEnd(){
		InputSource isrc = Server.readOneMessage(bis);
		double total_reward = 0;
		try {
			total_reward = processXMLSessionEnd(p, isrc);
		} catch (RDDLXMLException e1) {
			Main.exceptionLog.log("xml exception in processing session end\n"+e1.getMessage());
			System.exit(1);
		}
		try {
			connection.close();
		} catch (IOException e) {
			Main.exceptionLog.log("exception in closing socket connection\n"+e.getMessage());
			System.exit(1);
		}
		return total_reward;
	}
	
	static String serialize(Document dom) {
		OutputFormat format = new OutputFormat(dom);
//		format.setIndenting(true);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		
		XMLSerializer xmls = new XMLSerializer(baos, format);
		try {
			xmls.serialize(dom);
			return baos.toString();
		} catch (IOException e) {
			Main.exceptionLog.log("cannot serialize xml\n"+e.getMessage());
			System.exit(1);
		}
		return null;
	}
	
	static XMLType getXMLType(DOMParser p,InputSource isrc) {
		Element e = p.getDocument().getDocumentElement();
		if ( e.getNodeName().equals("turn") ) {
			return XMLType.TURN;
		} else if (e.getNodeName().equals("round")) {
			return XMLType.ROUND;
		} else if (e.getNodeName().equals("round-end")) {
			return XMLType.ROUND_END;
		} else if (e.getNodeName().equals("end-test")) {
			return XMLType.END_TEST;
		} else {
			return XMLType.NONAME;
		}
	}
	
	static String createXMLSessionRequest (String problemName, String clientName) {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {
			//get an instance of builder
			DocumentBuilder db = dbf.newDocumentBuilder();

			//create an instance of DOM
			Document dom = db.newDocument();
			Element rootEle = dom.createElement(Server.SESSION_REQUEST);
			dom.appendChild(rootEle);
			Server.addOneText(dom, rootEle, Server.PROBLEM_NAME, problemName);
			Server.addOneText(dom, rootEle, Server.CLIENT_NAME, clientName);
			return serialize(dom);
		} catch (Exception e) {
			Main.exceptionLog.log("Expcetion in creating session request\n"+e.getMessage());
			return null;
		}
	}
	
	static String createXMLRoundRequest() {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document dom = db.newDocument();
			Element rootEle = dom.createElement(Server.ROUND_REQUEST);
			dom.appendChild(rootEle);
			return serialize(dom);
		} catch (Exception e) {
			Main.exceptionLog.log("exceptin in creating round request\n"+e.getMessage());
			return null;
		}
	}
	
	static Relay processXMLSessionInit(DOMParser p, InputSource isrc) throws rddl.competition.RDDLXMLException {
		try {
			p.parse(isrc);
		} catch (SAXException e1) {
			Main.exceptionLog.log("SAX exception in processxmlsessioninit\n"+e1.getMessage());
			throw new RDDLXMLException("sax exception");
		} catch (IOException e1) {
			Main.exceptionLog.log("IOexception in processxmlsessioninit\n"+e1.getMessage());
			throw new RDDLXMLException("io exception");
		}
		Relay c = new Relay();
		Element e = p.getDocument().getDocumentElement();
		
		if ( !e.getNodeName().equals(Server.SESSION_INIT) ) {
			throw new RDDLXMLException("not session init");
		}
		ArrayList<String> r = Server.getTextValue(e, Server.SESSION_ID);
		if ( r != null ) {
			c.id = Integer.valueOf(r.get(0));
		}
		r = Server.getTextValue(e, Server.NUM_ROUNDS);
		if ( r != null ) {
			c.numRounds = Integer.valueOf(r.get(0));
		}
		r = Server.getTextValue(e, Server.TIME_ALLOWED);
		if ( r!= null ) {
			c.timeAllowed = Double.valueOf(r.get(0));
//			System.err.println(c.timeAllowed);
		}
		return c;
	}
	
	static String createXMLAction(ArrayList<PVAR_INST_DEF> ds) {
	//static String createXMLAction(State state, Policy policy) {
		try {  
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document dom = db.newDocument();
			Element actions = dom.createElement(Server.ACTIONS);
			dom.appendChild(actions);
			for ( PVAR_INST_DEF d : ds ) {
				Element action = dom.createElement(Server.ACTION);
				actions.appendChild(action);
				Element name = dom.createElement(Server.ACTION_NAME);
				action.appendChild(name);
				Text textName = dom.createTextNode(d._sPredName.toString());
				name.appendChild(textName);
				for( LCONST lc : d._alTerms ) {
					Element arg = dom.createElement(Server.ACTION_ARG);
					Text textArg = dom.createTextNode(lc.toString());
					arg.appendChild(textArg);
					action.appendChild(arg);
				}
				Element value = dom.createElement(Server.ACTION_VALUE);
				Text textValue = dom.createTextNode(d._oValue.toString());
				value.appendChild(textValue);
				action.appendChild(value);
			}
			// Sungwook: a noop is just an all-default action, not a special
			// action.  -Scott
			//if ( ds.size() == 0) {
			//	Element noop = dom.createElement(Server.NOOP);
			//	actions.appendChild(noop);
			//}

			return serialize(dom);
		} catch (ParserConfigurationException e) {
			Main.exceptionLog.log(e.getMessage());
		}
		return null;
	}
	
	static int getANumber (DOMParser p, InputSource isrc, 
			String parentName, String name) {
		Element e = p.getDocument().getDocumentElement();
		if ( e.getNodeName().equals(parentName) ) {
			String turnnum = Server.getTextValue(e, name).get(0);
			return Integer.valueOf(turnnum);
		}
		return -1;
	}

	static double processXMLRoundInit(DOMParser p, InputSource isrc,
			int curRound) throws RDDLXMLException {
		try {
			p.parse(isrc);
		} catch (SAXException e1) {
//			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("sax exception");
		} catch (IOException e1) {
//			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("io exception");
		}
		Element e = p.getDocument().getDocumentElement();
		if ( !e.getNodeName().equals(Server.ROUND_INIT)) {
			return -1;
		}
		ArrayList<String> r = Server.getTextValue(e, Server.ROUND_NUM);
		if ( r == null || curRound != Integer.valueOf(r.get(0))) {
			return -1;
		}
		r =	Server.getTextValue(e, Server.TIME_LEFT);
		if ( r == null ) {
			return -1;
		}
		return Double.valueOf(r.get(0));
	}
	
	static ArrayList<PVAR_INST_DEF> processXMLTurn (DOMParser p, InputSource isrc,
			State state) throws RDDLXMLException {
		try {
			p.parse(isrc);
		} catch (SAXException e1) {
			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("sax exception");
		} catch (IOException e1) {
			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("io exception");
		}
		Element e = p.getDocument().getDocumentElement();
		if ( e.getNodeName().equals(Server.TURN) ) {
			
			// We need to be able to distinguish no observations from
			// all default observations.  -Scott
			if (e.getElementsByTagName(Server.NULL_OBSERVATIONS).getLength() > 0) {
				return null;
			}
			
			// FYI: I think nl is never null.  -Scott
			NodeList nl = e.getElementsByTagName(Server.OBSERVED_FLUENT);
			if(nl != null && nl.getLength() > 0) {
				ArrayList<PVAR_INST_DEF> ds = new ArrayList<PVAR_INST_DEF>();
				for(int i = 0 ; i < nl.getLength();i++) {
					Element el = (Element)nl.item(i);
					String name = Server.getTextValue(el, Server.FLUENT_NAME).get(0);
					ArrayList<String> args = Server.getTextValue(el, Server.FLUENT_ARG);
					ArrayList<LCONST> lcArgs = new ArrayList<LCONST>();
					for( String arg : args ) {
						if (arg.startsWith("@"))
							lcArgs.add(new RDDL.ENUM_VAL(arg));
						else 
							lcArgs.add(new RDDL.OBJECT_VAL(arg));
					}
					String value = Server.getTextValue(el, Server.FLUENT_VALUE).get(0);
					Object r = Server.getValue(name, value, state);
					PVAR_INST_DEF d = new PVAR_INST_DEF(name, r, lcArgs);
					ds.add(d);
				}
				return ds;
			} else
				return new ArrayList<PVAR_INST_DEF>();
		}
		return null;
	}
	
	static double processXMLRoundEnd(DOMParser p, InputSource isrc) throws RDDLXMLException {
		try {
			p.parse(isrc);
		} catch (SAXException e1) {
			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("sax exception");
		} catch (IOException e1) {
			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("io exception");
		}
		Element e = p.getDocument().getDocumentElement();
		if ( e.getNodeName().equals(Server.ROUND_END) ) {
			ArrayList<String> text = Server.getTextValue(e, Server.ROUND_REWARD);
			if ( text == null ) {
				return -1;
			}
			return Double.valueOf(text.get(0));
		}
		return -1;
	}
			
	static double processXMLSessionEnd(DOMParser p, InputSource isrc) throws RDDLXMLException {
		try {
			p.parse(isrc);
		} catch (SAXException e1) {
			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("sax exception");
		} catch (IOException e1) {
			Main.exceptionLog.log(e1.getMessage());
			throw new RDDLXMLException("io exception");
		}
		Element e = p.getDocument().getDocumentElement();
		if ( e.getNodeName().equals(Server.SESSION_END) ) {
			ArrayList<String> text = Server.getTextValue(e, Server.TOTAL_REWARD);
			if ( text == null ) {
				return -1;
			}
			return Double.valueOf(text.get(0));
		}
		return -1;
	}
}

